package multiconnector.broadcast;

import android.content.Intent;
import android.util.Log;

import java.io.IOException;
import java.net.*;
import java.util.Arrays;

/**
 * encapsulate logic necessary for socket communication
 */
public class ReceiverSocket extends BroadcastSocket {

	private final int maxPackageSize;
	private final String mac;
	private final String applicationTag;
	private final IntentProcessor intentProcessor;

	public interface PacketListener {
		public void onPacketReceive(InetAddress pFrom, int pFromPort, Intent pData, boolean wasEncrypted);
	}

	/**
	 * preprocess incoming ingtent
	 */
	public interface IntentProcessor {
		Intent processIntent(Intent intent);
	}


	/**
	 * default implementation doing nothing
	 */
	private static final class NullIntentProcessor implements IntentProcessor {
		public Intent processIntent(Intent intent) {
			return intent;
		}
	}

	private static final String LOG_TAG = ReceiverSocket.class.getName();
	private PacketListener mHandler;

	public ReceiverSocket(InetAddress inetAddress, int port, int maxPackageSize, String mac, String applicationTag) {
		this(inetAddress, port, maxPackageSize, mac, applicationTag, new NullIntentProcessor());
	}

	public ReceiverSocket(InetAddress inetAddress, int port, int maxPackageSize, String mac, String applicationTag, IntentProcessor intentProcessor) {
		super(inetAddress, port);
		this.maxPackageSize = maxPackageSize;
		this.mac = mac;
		this.applicationTag = applicationTag;
		this.intentProcessor = intentProcessor;
	}


	@Override
	public void run() {
		start();

		while (isRunning()) {
			Log.d(LOG_TAG, "receiver fires");

			byte[] buf = new byte[maxPackageSize];
			DatagramPacket incoming = new DatagramPacket(buf, buf.length);

			try {

				getSocket().receive(incoming);

				InetAddress from = incoming.getAddress();
				int port = incoming.getPort();
				String message = new String(Arrays.copyOf(incoming.getData(),
						incoming.getLength()));

				// notify handler
				if (getOnReceiveHandler() != null) {
					Intent intent = Intent.parseUri(message, 0);
					intent = intentProcessor.processIntent(intent);
					if (intent == null)
						continue;
					// packet has illegal action -> drop it
					if (!intent.getAction().startsWith(
							Broadcaster.INTENT_ACTION)) {
						// intent has corrupted action
						Log.e(LOG_TAG, "intent has corrupted action");
						continue;
					}

					// remove protocol specific extras
					if (intent.hasExtra(Broadcaster.INTENT_EXTRA_APP_TAG)) {
						// intent not for this application
						if (!intent.getStringExtra(
								Broadcaster.INTENT_EXTRA_APP_TAG).equals(
								applicationTag)) {
							Log.e(LOG_TAG, "intent not for this application");
							continue;
						}
						intent.removeExtra(Broadcaster.INTENT_EXTRA_APP_TAG);
					}
					if (intent.hasExtra(Broadcaster.INTENT_EXTRA_TO_TAG)) {
						// not for this receipient
						if (!intent.getStringExtra(
								Broadcaster.INTENT_EXTRA_TO_TAG).equals(mac)) {
							Log.e(LOG_TAG, "not for this receipient");
							continue;
						}
						intent.removeExtra(Broadcaster.INTENT_EXTRA_TO_TAG);
					}


					// valid packet for this application
					boolean wasEncrypted = Channel.wasEncrypted(intent);
					getOnReceiveHandler().onPacketReceive(from, port, intent, wasEncrypted);
					Log.d(LOG_TAG, "received packet");
					Log.d(LOG_TAG, from + ":" + port + " contains: " + message);
				} else {
					// no one wants to work with that packet
				}
			} catch (IOException e) {
				Log.e(LOG_TAG, "exception while receiving", e);
			} catch (URISyntaxException e) {
				// Received message is no intent -> ignore packet
				Log.e(LOG_TAG, "Received Intent but couldnt parse given Uri.");
			}
		}
	}

	@Override
	public DatagramSocket createSocket() throws SocketException {
		return new DatagramSocket(getPort(), getBroadcastAddress());
	}

	public PacketListener getOnReceiveHandler() {
		return mHandler;
	}

	public void setOnReceiveHandler(PacketListener mHandler) {
		this.mHandler = mHandler;
	}
}
